package com.goldfish666.houghrecognition.util;

import java.awt.image.BufferedImage;
import java.util.*;

/**
 * 形状工具
 * 用于从内容图片抽象出形状
 */
public class ShapeUtil {

    static boolean doubleEqual(double a, double b, double tolerant) {
        return Math.abs(a - b) < tolerant;
    }

    static int doubleToInt(double d) {
        int result = (int) d;
        return d - result >= 0.5 ? result + 1 : result;
    }

    static class Shape {


        private final String name;

        private final List<PointInPolar> points;

        public List<PointInCartesian> getReferencePointList(PointInCartesian pointInCartesian) {
            List<PointInCartesian> result = new ArrayList<>();

            for (PointInPolar point : points) {
                int x0 = pointInCartesian.getX() - doubleToInt(point.getR() * Math.sin(point.getTheta()));
                int y0 = pointInCartesian.getY() - doubleToInt(point.getR() * Math.cos(point.getTheta()));
                result.add(new PointInCartesian(x0, y0));
            }
            return result;
        }

        private Shape(String name, List<PointInCartesian> pointInCartesianList) {
            this.points = buildPoints(pointInCartesianList);
            this.name = name;
        }

        public String getName() {
            return name;
        }

        private int findMinX(List<PointInCartesian> pointInCartesianList) {
            return pointInCartesianList.stream().map(PointInCartesian::getX).min(Comparator.comparingInt(e -> e)).get();
        }

        private int findMaxX(List<PointInCartesian> pointInCartesianList) {
            return pointInCartesianList.stream().map(PointInCartesian::getX).max(Comparator.comparingInt(e -> e)).get();
        }

        private int findX0(List<PointInCartesian> pointInCartesianList) {
            return (findMinX(pointInCartesianList) + findMaxX(pointInCartesianList)) / 2;
        }

        private int findMinY(List<PointInCartesian> pointInCartesianList) {
            return pointInCartesianList.stream().map(PointInCartesian::getY).min(Comparator.comparingInt(e -> e)).get();
        }

        private int findMaxY(List<PointInCartesian> pointInCartesianList) {
            return pointInCartesianList.stream().map(PointInCartesian::getY).max(Comparator.comparingInt(e -> e)).get();
        }

        private int findY0(List<PointInCartesian> pointInCartesianList) {
            return (findMinY(pointInCartesianList) + findMaxY(pointInCartesianList)) / 2;
        }

        private List<PointInPolar> buildPoints(List<PointInCartesian> pointInCartesianList) {
            List<PointInPolar> result = new ArrayList<>();
            int x0 = findX0(pointInCartesianList);
            int y0 = findY0(pointInCartesianList);
            PointInCartesian ZERO = new PointInCartesian(x0, y0);
            for (PointInCartesian point : pointInCartesianList) {
                result.add(buildPointInPolar(ZERO, point));
            }
            return result;
        }

        private PointInPolar buildPointInPolar(PointInCartesian zero, PointInCartesian point) {
            int x0 = zero.getX();
            int y0 = zero.getY();
            int x1 = point.getX();
            int y1 = point.getY();
            double r = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2));
            double sin = (x1 - x0) / r;
            double cos = (y1 - y0) / r;


            return new PointInPolar(getTheta(sin, cos), r);
        }

        private double getTheta(double sin, double cos) {
            double theta = 0.0;
            List<Double> thetaList = new ArrayList<>(Arrays.asList(Math.asin(sin), Math.PI - Math.asin(sin)));
            thetaList.addAll(Arrays.asList(Math.acos(cos), 2 * Math.PI - Math.acos(cos)));

            if (sin < 0) {
                if (cos < 0 || doubleEqual(cos, 0, Math.PI / 180)) {
                    theta = thetaList.stream().filter(t -> Math.PI < t && 3 * Math.PI / 2 >= t).findAny().get();
                } else {
                    theta = thetaList.stream().filter(t -> 3 * Math.PI / 2 < t && 2 * Math.PI > t).findAny().get();
                }
            } else if (sin > 0) {
                if (cos < 0 || doubleEqual(cos, 0, Math.PI / 180)) {
                    theta = thetaList.stream().filter(t -> Math.PI / 2 <= t && Math.PI > t).findAny().get();
                } else {
                    theta = thetaList.stream().filter(t -> 0 <= t && Math.PI / 2 > t).findAny().get();
                }
            } else {
                if (cos < 0) {
                    theta = Math.PI;
                } else {
                    theta = 0;
                }
            }

            return theta;
        }

    }

    static class PointInCartesian {

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PointInCartesian that = (PointInCartesian) o;
            return x == that.x && y == that.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }

        private final int x;
        private final int y;

        PointInCartesian(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }
    }

    static class PointInPolar {
        private final double theta;
        private final double r;

        private PointInPolar(double theta, double r) {
            this.theta = theta;
            this.r = r;
        }

        public double getTheta() {
            return theta;
        }

        public double getR() {
            return r;
        }
    }

    public static Shape getShape(String name, BufferedImage bufferedImage) {
        List<PointInCartesian> pointInCartesianList = new ArrayList<>();
        int T = HoughImageUtil.getThreshold(bufferedImage);
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                if (HoughImageUtil.getGray(bufferedImage.getRGB(i, j)) < T) {
                    pointInCartesianList.add(new PointInCartesian(i, j));
                }
            }
        }
        return new Shape(name, pointInCartesianList);
    }
}
